package solver1;

import static extra.IntList.range;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

public class SudokuState {
	public Collection<Point> todo = new ArrayList<>();
	public Map<Point, int[]> state = new HashMap<Point, int[]>();

	/**
	 * list[0] = topleft list[80]=bottomright list[8] = topright list[73] * @param
	 * list[]
	 */
	public SudokuState(int[] list) {
		for (int i = 0; i < list.length; i++) {
			state.put(point(i), make(list[i]));
			if (list[i] != 0)
				todo.add(point(i));
		}

	}

	private static Point point(int i) {
		return new Point(i % 9, (i - (i % 9)) / 9);
	}

	private int[] make(int i) {
		if (i == 0)
			return range(1, 9);
		return range(i, i);
	}

	public void resolve(Rule rule, Point location) throws Exception {
		switch (rule) {
		case Horizontal:
			for (Point point : horizontal(location))
				remove(point, this.valueAt(location));
			break;
		case Vertical:
			for (Point point : vertical(location))
				remove(point, this.valueAt(location));
			break;
		case Square:
			for (Point point : square(location))
				remove(point, this.valueAt(location));
			break;
		default:
			throw new Error("Sudokustate rule cannot be resolved:"
					+ rule.name());
		}
	}

	private Collection<Point> square(Point location) {
		Collection<Point> rv = new ArrayList<>();
		for (int x = (location.x / 3) * 3; x < ((location.x / 3) * 3 + 3); x++)
			for (int y = (location.y / 3) * 3; y < ((location.y / 3) * 3 + 3); y++)
				if (x != location.x || y != location.y)
					rv.add(new Point(x, y));
		return rv;
	}

	private Collection<Point> vertical(Point location) {
		Collection<Point> rv = new ArrayList<>();
		for (int y = 0; y < 9; y++)
			if (y != location.y)
				rv.add(point(location.x + y * 9));
		return rv;
	}

	private void remove(Point point, int valueAt) throws Exception {
		int[] originalArray = state.remove(point);
		if (originalArray.length == 1 && originalArray[0] == valueAt)
			throw new Exception();
		int index = -1;
		for (int i = 0; i < originalArray.length; i++) {
			if (originalArray[i] == valueAt) {
				index = i;
				int[] ar = new int[originalArray.length - 1];
				for (int j = 0; j < ar.length; j++) {
					if (j < index)
						ar[j] = originalArray[j];
					else
						ar[j] = originalArray[j + 1];
				}
				if (ar.length == 1)
					todo.add(point);
				state.put(point, ar);
				return;
			}
		}
		state.put(point, originalArray);
	}

	private int valueAt(Point location) {
		if (state.get(location).length != 1)
			System.out.println("abra");
		return state.get(location)[0];
	}

	private Collection<Point> horizontal(Point at) {
		Collection<Point> rv = new ArrayList<>();
		for (int x = 0; x < 9; x++)
			if (x != at.x)
				rv.add(point(9 * at.y + x));
		return rv;
	}

	public boolean hasUnresolvedIssues() {
		return this.todo.size() != 0;
	}

	public int[] toList() {
		int[] rv = new int[81];
		for (Entry<Point, int[]> entry : state.entrySet()) {
			int i = entry.getKey().x + entry.getKey().y * 9;
			if (entry.getValue().length == 1)
				rv[i] = entry.getValue()[0];
			else
				rv[i] = 0;
		}
		return rv;
	}

	public boolean solved() {
		for (int[] a : state.values())
			if (a.length != 1)
				return false;
		return true;
	}

	public Collection<SudokuState> branchAt(Point chooseLocation) {
		Collection<SudokuState> rv = new ArrayList<>();
		for (int i : this.state.get(chooseLocation)) {
			SudokuState s = new SudokuState(this.toList());
			s.state.put(chooseLocation, ar(i));
			rv.add(s);
		}
		return rv;
	}

	private int[] ar(int... i) {
		return i;
	}

	public void resolveIssues() throws Exception {
		while (hasUnresolvedIssues()) 
		{
			for (Point point : consume(todo))
				for (Rule rule : Rule.values())
					resolve(rule, point);
			
		}

	}

	private <T> Collection<T> consume(Collection<T> todo) {
		ArrayList<T> rv = new ArrayList<>(todo);
		todo.clear();
		return rv;
	}
}
